/* Socks Server 5
 * Copyright (C) 2002 - 2011 by Matteo Ricchetti - <matteo.ricchetti@libero.it>

 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include"SS5Main.h"
#include"SS5Core.h"
#include"SS5Modules.h"
#include"SS5Server.h"
#include"SS5Utils.h"
#include"SS5Thread.h"

INT main(int argc, char **argv, char **envp) 
{
    struct sockaddr_in clientSsin;

    struct sigaction actionHungup;
    struct sigaction actionChild;
    struct sigaction actionPipe;

    register UINT idx1;
    register UINT idx2;

    UINT i,l;

    UINT totalChildren = 0;
    UINT newChildren   = 0;
    UINT forkChildren  = OK;
    UINT mode          = LOAD_CONFIG;

    int count;
    int status;
    int clientSocket;

    // ss5绑定的IF和端口，如0.0.0.0:1080
    char socksAddr[16] = SS5_DEFAULT_ADDR;
    char socksPort[6]  = SS5_DEFAULT_PORT;
    // ss5进程所属用户。
    char socksUser[32] = SS5_DEFAULT_USER;

    pid_t childPid;

    extern char S5PidFileName[MAXPPATHLEN];
    extern FILE *S5PidFile;
	
    /* 
     * 初始化一些默认值。比如线程模式设置为ERR（0），预分配进程数（PreforkProcesses）设置为1等等。
     * 还有一些诸如配置文件、密码文件、动态链接库(so)路径等等。
     */
    S5SetStatic();

    /* 
     *    Get environment data config
     */
    for(count = 0; envp[count]; count++ )
      if( STREQ(envp[count],"SS5_SOCKS_USER=",sizeof("SS5_SOCKS_USER=") - 1) ) {
        strncpy(socksUser,(char *)getenv("SS5_SOCKS_USER"),sizeof(socksUser));
        fprintf(stderr,"[INFO] found environment SS5_SOCKS_USER:    %30s\n",socksUser);
      }
      else if( STREQ(envp[count],"SS5_SOCKS_PORT=",sizeof("SS5_SOCKS_PORT=") -1 ) ) {
        strncpy(socksPort,(char *)getenv("SS5_SOCKS_PORT"),sizeof(socksPort));
        fprintf(stderr,"[INFO] found environment SS5_SOCKS_PORT:    %30s\n",socksPort);
      }
      else if( STREQ(envp[count],"SS5_SOCKS_ADDR=",sizeof("SS5_SOCKS_ADDR=") - 1) ) {
        strncpy(socksAddr,(char *)getenv("SS5_SOCKS_ADDR"),sizeof(socksAddr));
        fprintf(stderr,"[INFO] found environment SS5_SOCKS_ADDR:    %30s\n",socksAddr);
      }
      else if( STREQ(envp[count],"SS5_PROPAGATE_KEY=",sizeof("SS5_PROPAGATE_KEY=") - 1) ) {
        strncpy(S5RepKey,(char *)getenv("SS5_PROPAGATE_KEY"),sizeof(S5RepKey));
        SS5SocksOpt.PropagateKey  = atol(S5RepKey);
        fprintf(stderr,"[INFO] found environment SS5_PROPAGATE_KEY\n");
      }
      else if( STREQ(envp[count],"SS5_ROLE_SLAVE=",sizeof("SS5_ROLE_SLAVE=") - 1) ) {
        SS5SocksOpt.Role  = SLAVE;
        fprintf(stderr,"[INFO] found environment SS5_ROLE_SLAVE\n");
      }
      else if( STREQ(envp[count],"SS5_CONFIG_FILE=",sizeof("SS5_CONFIG_FILE=") - 1) ) {
        strncpy(S5ConfigFile,(char *)getenv("SS5_CONFIG_FILE"),sizeof(S5ConfigFile));
        fprintf(stderr,"[INFO] found environment SS5_CONFIG_FILE:   %30s\n",S5ConfigFile);
      }
      else if( STREQ(envp[count],"SS5_LOG_FILE=",sizeof("SS5_LOG_FILE=") - 1) ) {
        strncpy(S5LoggingFile,(char *)getenv("SS5_LOG_FILE"),sizeof(S5LoggingFile));
        fprintf(stderr,"[INFO] found environment SS5_LOG_FILE:      %30s\n",S5LoggingFile);
      }
      else if( STREQ(envp[count],"SS5_PEERS_FILE=",sizeof("SS5_PEERS_FILE=") - 1) ) {
        strncpy(S5PeersFile,(char *)getenv("SS5_PEERS_FILE"),sizeof(S5PeersFile));
        fprintf(stderr,"[INFO] found environment SS5_PEERS_FILE:    %30s\n",S5PeersFile);
      }
      else if( STREQ(envp[count],"SS5_PASSWORD_FILE=",sizeof("SS5_PASSWORD_FILE=") - 1) ) {
        strncpy(S5PasswordFile,(char *)getenv("SS5_PASSWORD_FILE"),sizeof(S5PasswordFile));
        fprintf(stderr,"[INFO] found environment SS5_PASSWORD_FILE: %30s\n",S5PasswordFile);
      }
      else if( STREQ(envp[count],"SS5_PROFILE_PATH=",sizeof("SS5_PROFILE_PATH=") - 1) ) {
        strncpy(S5ProfilePath,(char *)getenv("SS5_PROFILE_PATH"),sizeof(S5ProfilePath));
        fprintf(stderr,"[INFO] found environment SS5_PROFILE_PATH: %30s\n",S5ProfilePath);
      }
      else if( STREQ(envp[count],"SS5_LIB_PATH=",sizeof("SS5_LIB_PATH=") - 1) ) {
        strncpy(S5LibPath,(char *)getenv("SS5_LIB_PATH"),sizeof(S5LibPath));
        fprintf(stderr,"[INFO] found environment SS5_LIB_PATH:      %30s\n",S5LibPath);
      }

    STRSCAT(S5LibPath,"/ss5");

    /* 
     *    Parse command line parameters
     */
    for(count = 1 ;count < argc; count++) {
      if( argv[count][0] == '-' ) {
        switch( argv[count][1] ) {
          case 'c':   mode = PARSE_CONFIG;        break;    /*    仅解析配置文件以检查配置文件是否正确，而不实际运行服务。    */
          case 'm':   SS5SocksOpt.Mute = OK;      break;    /*    禁用日志 */
          case 's':   SS5SocksOpt.Syslog = OK;    break;    /*    记录日志到syslog而非文件中    */
          case 'v':
          // 输出版本版权信息。
            fprintf(stderr,"[INFO] %s\n",SS5_VERSION);
	    fprintf(stderr,"[INFO] %s\n",SS5_COPYRIGHT);

            S5ServerClose(EXIT);
	  break;
          case 't':
	    /*
	     *    设置以多线程模式执行而非多进程模式。
	     */
            if( SS5SocksOpt.PreforkProcesses > 1 )
              S5Usage();

            SS5SocksOpt.IsThreaded=OK;
	  break;
          case 'n':
	    /*
	     *    设置预先fork的进程数，这些进程用于接受客户端的连接。
	     *    例如“ss5 -n 10”表示预先fork十个接受客户端连接的进程。
	     */
            if (!argv[count+1] || THREADED() ) 
              S5Usage();
            else {
              if( strtol(argv[count+1], (char **)NULL, 10) > MAXPREFORKPROCS )
	        SS5SocksOpt.PreforkProcesses=MAXPREFORKPROCS;
	      else
	        SS5SocksOpt.PreforkProcesses = atoi(argv[count+1]);
                count++;
	    }
	  break;
          case 'u':
	    /*
	     *    设置此进程运行的user id。默认值是nobody.
	     *    例如"ss5 -u nobody"设置当前进程用户为nobody.
	     */
            if (!argv[count+1]) 
              S5Usage();
            else {
                strncpy(socksUser,argv[count+1],sizeof(socksUser));
                count++;
	    }
	  break;
          case 'b':
            if (!argv[count+1]) 
              S5Usage();
            else {
              /*
	       *    设置ss5监听时绑定的IF和端口
	       *
	       *    例如 ss5 -b 172.30.1.1:1082, 在172.30.1.1的1082端口上监听。
	       *    例如 ss5 -b 0.0.0.0:1082, 在所有IF的1082端口上监听。
	       */
	      for(idx1 = 0; idx1 < strlen(argv[count+1]) 
                            && (socksAddr[idx1] = argv[count+1][idx1]) != ':'
                            && argv[count+1][idx1]; idx1++);
	      socksAddr[idx1] = '\0';
	      idx1++;
	      for(idx2 = 0; idx2 < sizeof(socksPort) && (socksPort[idx2] = argv[count+1][idx1]); idx1++,idx2++)
                ;    /*    VOID    */
	      socksPort[idx2] = '\0';
	      count++;
	    }
	  break;
          case 'p':
            if (!argv[count+1]) 
              S5Usage();
            else {
              /*
               *  设置pid文件路径，默认为/var/run/ss5/ss5.pid
               */
              if((strlen(argv[count+1])+1)>MAXPPATHLEN) {
                fprintf(stderr,"Too long pid file path\n");
                S5ServerClose(EXIT);
				}
                strncpy(S5PidFileName,argv[count+1],sizeof(S5PidFileName));
		count++;
	    }
	  break;

          default:    S5Usage();    break;
	}
      }
      else
        S5Usage();
    }

    /*
     *    加载默认的模块(.so): 如果你想禁用一个模块只需要重命名它，ss5将会忽略它。
     */
    S5LoadModules();

    /*
     * 初始化在LOCKMUTEXCO()和UNLOCKMUTEXCO()中需要用到的pthread互斥锁。
     * 这个锁用于保证仅有一个进程(线程)执行配置文件的加载过程。
    */
    pthread_mutex_init(&COMutex,NULL);

    /* 
     * SS5服务器切换到后台执行。
     */
    if( mode == LOAD_CONFIG ) {
      if( !S5MakeDaemon() )
        S5ServerClose(EXIT); //如执行失败则关闭程序，删除pid文件。这个函数的参数为退出码。
    }
    else {
      // 检查配置文件，完成后退出。由-c选项指定。
      if( !S5LoadConfig(PARSE_CONFIG) )
        fprintf(stderr,"[ERRO] Error parsing configuration file (see ss5.log file for details).\n");
      else
        fprintf(stderr,"[INFO] Syntax OK\n");

      S5ServerClose(EXIT);
    }
    
    /* 
     *    Read and parse configuration file (/etc/opt/ss5/ss5.conf)
     */
    if( !S5LoadConfig(LOAD_CONFIG) ) {
        fprintf(stderr,"[ERRO] Error parsing configuration file (see ss5.log file for details). SS5 exiting...\n");
        S5ServerClose(EXIT);
    }

    /* 
     * 将当前pid(即主进程的pid)写入PID文件，主进程只有一个，在使用默认的多进程模式时会有多个子进程用于处理客户端的连接。
     */
    S5PidFile=fopen(S5PidFileName,"w+");

    if (S5PidFile == NULL) {
      fprintf(stderr,"Can't create pid file %s\n",S5PidFileName);
      S5ServerClose(EXIT);
    }
    if (fprintf(S5PidFile,"%d",(int)getpid())<=0) {
      fprintf(stderr,"Can't write to pid file %s\n",S5PidFileName);
      S5ServerClose(EXIT);
    }
    if (fclose(S5PidFile)!=0) {
      fprintf(stderr,"Can't close pid file %s\n",S5PidFileName);
      S5ServerClose(EXIT);
    }

    /* 
     * 获取所有网卡接口的信息。
     */
    if( !S5GetIf() )
      S5ServerClose(EXIT);

    /* 
     * 设置当前进程的user ID为指定的用户名，此用户名默认为nobody。
     */
    if( !S5UIDSet(socksUser) ) {
      fprintf(stderr,"[ERRO] User %s not found.\n",socksUser);
      S5ServerClose(EXIT);
    }

    /* 
     * 创建socks服务器socket, 绑定它到指定的IF并在其上监听新的连接。
     */
    if( !S5ServerMake(socksAddr, atoi(socksPort)) )
      S5ServerClose(EXIT);

    /* 
     * 如果只用一个子进程则忽略子进程退出的信号。
     */
    if( SS5SocksOpt.PreforkProcesses == 1 )  {
      actionChild.sa_flags   = SA_RESTART;
      actionChild.sa_handler = SIG_IGN;
      sigaction(SIGCHLD,&actionChild,NULL);
    }

    /* 
     * 设置HUNGUP信号处理函数为重新载入配置。
     * 同时忽略SIGPIPE信号。
     */
    actionHungup.sa_flags   = SA_RESTART;
    actionHungup.sa_handler = S5ReloadConfig;
    sigaction(SIGHUP,&actionHungup,NULL);

    actionPipe.sa_flags   = SA_RESTART;
    actionPipe.sa_handler = SIG_IGN;
    sigaction(SIGPIPE,&actionPipe,NULL);

    totalChildren = SS5SocksOpt.PreforkProcesses;

    for( ;; ) {
      /* 
       * 判断是否为进程模式，如果不是就用线程模式。
       */
      if( NOTTHREADED() ) {
        /* 
         * 进程模式下接受一个连接。
         */ 
        if( SS5SocksOpt.PreforkProcesses == 1 ) {
          // 这是非预fork模式，accept成功创建新连接后才会创建一个新的子进程去处理新连接。
          // 这应该算是最常规的模式，也是默认的模式。
          if( S5ServerAccept(&clientSsin, &clientSocket) ) {
            if( !(childPid = fork()) ) {
                // 这里是子进程运行的代码。注意这种模式下S5Core的参数是新建立的socket句柄。
                S5Core(clientSocket);
            }
            else {
            /* 
             * 这里是父进程运行的代码。我们在这里释放子进程的上下文。
             */
              S5ChildClose(CONTINUE,clientSocket,NULL);
            }
          }
        }
        else {
          /*
           * 如果使用-n选项设置了>1的数值，那么进入预forked模式: 创建一些新进程让create new processes and each one
           * 每一个都应用SS5SocksOpt。这种模式下需要使用PreforkProcessLife。
           */
          if( forkChildren ) { 
            forkChildren = ERR;
            for(idx1 = 0; idx1 < (SS5SocksOpt.PreforkProcesses - newChildren); idx1++) {
              if( !(childPid = fork()) ) {
               // 这里是子进程的代码。注意这种预分配进程模式下S5Core参数为0.
               for(idx2 = 0; idx2 < SS5SocksOpt.PreforkProcessLife; idx2++) {
                  S5Core(0);
                }
                S5ChildClose(EXIT,0,NULL);
              }
              else {
              /* 
               * 这里是父进程运行的代码。我们在这里释放子进程的上下文。
               */
              S5ChildClose(CONTINUE,0,NULL);
              }
            }
          }
          /*
           *    Wait for  and count any terminated child process: if n° childrens < 50% of preforked processes
           *    add new childrens
           */
          if( waitpid(-1,&status,0) == 0 ) {
    
          }
          else {
            /*
             *    Returns true if the child terminated normally or  because of a signal which was not caught
             *    and recalculate total childrens
             */
            if( WIFEXITED(status) || WIFSIGNALED(status) ) {
              totalChildren--;
              if( totalChildren <= (SS5SocksOpt.PreforkProcesses/2) ) {
                forkChildren  = OK;
                totalChildren = SS5SocksOpt.PreforkProcesses;
                newChildren   = (SS5SocksOpt.PreforkProcesses/2);
              }
            }
          }
        }
      }
      else {
        /*
         * 以线程模式启动socks5服务器,使用-t启用。通常意义上而言线程模式更为高效。
         */
        S5MainThread(S5SocksSocket);
      }
    } 

    S5ServerClose(EXIT);

    return OK;
}

